#ifdef ZWAY_ON_ETHERNET

#include <string.h>

#include "esp_event.h"
#include "esp_eth.h"
#if CONFIG_ETH_USE_SPI_ETHERNET
#include "driver/spi_master.h"
#endif // CONFIG_ETH_USE_SPI_ETHERNET
#include "esp_log.h"
#include "esp_mac.h"
#include "driver/gpio.h"
#include "esp_netif.h"

#include "ZPlatform.h"
#include "ZLogging.h"
#include "ZWayEsp32EthernetPrivate.h"

static esp_netif_t *__eth_start(_ZWayEsp32EthCtx_t *const ctx);

#define ZWAY_ESP32_NETIF_DESC_ETH "zway_netif_eth"

static void eth_on_got_ip(void *const arg, const esp_event_base_t event_base, const int32_t event_id, void *const event_data)
{
    _ZWayEsp32EthCtx_t *const ctx = (_ZWayEsp32EthCtx_t *)arg;
    ip_event_got_ip_t *const event = (ip_event_got_ip_t *)event_data;
    if (event->esp_netif != ctx->netif)
    {
        return;
    }
    ctx->ip4_addr = event->ip_info.ip;
    if (ctx->semph_wait_ip == NULL)
    {
        return;
    }
    xSemaphoreGive(ctx->semph_wait_ip);
}

#define ESP_ZWAY_ERRCHECK(FUNC)\
{ \
    const esp_err_t err_rc = (FUNC); \
    if (err_rc != ESP_OK)\
    { \
        zlog_write(ctx->logger, __FILE__, Error, "ESP Error:%d", err_rc); \
    } \
}

static esp_netif_t *__eth_start(_ZWayEsp32EthCtx_t *const ctx)
{
    esp_netif_t *netif;
    {
        esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_ETH();
        // Warning: the interface desc is used in tests to capture actual connection details (IP, gw, mask)
        esp_netif_config.if_desc = ZWAY_ESP32_NETIF_DESC_ETH;
        esp_netif_config.route_prio = 64;
        esp_netif_config_t netif_config =
        {
            .base = &esp_netif_config,
            .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH
        };
        netif = esp_netif_new(&netif_config);
        if (netif == NULL)
        {
           return NULL;
        }
    }
    {
        eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
        mac_config.rx_task_stack_size = ZWAY_ETHERNET_EMAC_TASK_STACK_SIZE;
        eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
        phy_config.phy_addr = 1;
        phy_config.reset_gpio_num = ZWAY_ETH_PHY_RST_GPIO;
#if ZME_ETH_INTERNAL_ETHERNET
        eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
        esp32_emac_config.smi_gpio.mdc_num = ZWAY_ETH_MDC_GPIO;
        esp32_emac_config.smi_gpio.mdio_num = ZWAY_ETH_MDIO_GPIO;
        ctx->mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
#if ZME_ETH_PHY_IP101
        ctx->phy = esp_eth_phy_new_ip101(&phy_config);
#elif ZME_ETH_PHY_RTL8201
        ctx->phy = esp_eth_phy_new_rtl8201(&phy_config);
#elif ZME_ETH_PHY_LAN87XX
        ctx->phy = esp_eth_phy_new_lan87xx(&phy_config);
#elif ZME_ETH_PHY_DP83848
        ctx->phy = esp_eth_phy_new_dp83848(&phy_config);
#elif ZME_ETH_PHY_KSZ80XX
        ctx->phy = esp_eth_phy_new_ksz80xx(&phy_config);
#endif
#elif ZME_ETH_SPI_ETHERNET
        ESP_ZWAY_ERRCHECK(gpio_install_isr_service(0));
        spi_bus_config_t buscfg =
        {
            .miso_io_num = ZWAY_ETH_SPI_MISO_GPIO,
            .mosi_io_num = ZWAY_ETH_SPI_MOSI_GPIO,
            .sclk_io_num = ZWAY_ETH_SPI_SCLK_GPIO,
            .quadwp_io_num = -1,
            .quadhd_io_num = -1,
        };
        ESP_ZWAY_ERRCHECK(spi_bus_initialize(CONFIG_EXAMPLE_ETH_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
        spi_device_interface_config_t spi_devcfg =
        {
            .mode = 0,
            .clock_speed_hz = ZWAY_ETH_SPI_CLOCK_MHZ * 1000 * 1000,
            .spics_io_num = ZWAY_ETH_SPI_CS_GPIO,
            .queue_size = 20
        };
#if ZME_ETH_DM9051
        /* dm9051 ethernet driver is based on spi driver */
        eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(CONFIG_EXAMPLE_ETH_SPI_HOST, &spi_devcfg);
        dm9051_config.int_gpio_num = ZWAY_ETH_SPI_INT_GPIO;
        ctx->mac = esp_eth_mac_new_dm9051(&dm9051_config, &mac_config);
        ctx->phy = esp_eth_phy_new_dm9051(&phy_config);
#elif ZME_ETH_W5500
        /* w5500 ethernet driver is based on spi driver */
        eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(CONFIG_EXAMPLE_ETH_SPI_HOST, &spi_devcfg);
        w5500_config.int_gpio_num = ZWAY_ETH_SPI_INT_GPIO;
        ctx->mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
        ctx->phy = esp_eth_phy_new_w5500(&phy_config);
#endif
#elif ZME_ETH_OPENETH
        phy_config.autonego_timeout_ms = 100;
        ctx->mac = esp_eth_mac_new_openeth(&mac_config);
        ctx->phy = esp_eth_phy_new_dp83848(&phy_config);
#endif
    }
    // Install Ethernet driver
    {
        esp_eth_config_t config = ETH_DEFAULT_CONFIG(ctx->mac, ctx->phy);
        ESP_ZWAY_ERRCHECK(esp_eth_driver_install(&config, &ctx->eth_handle));
    }
#if !CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET
    /* The SPI Ethernet module might doesn't have a burned factory MAC address, we cat to set it manually.
       We set the ESP_MAC_ETH mac address as the default, if you want to use ESP_MAC_EFUSE_CUSTOM mac address, please enable the
       configuration: `ESP_MAC_USE_CUSTOM_MAC_AS_BASE_MAC`
    */
    {
        uint8_t eth_mac[6] = {0};
        ESP_ZWAY_ERRCHECK(esp_read_mac(eth_mac, ESP_MAC_ETH));
        ESP_ZWAY_ERRCHECK(esp_eth_ioctl(ctx->eth_handle, ETH_CMD_S_MAC_ADDR, eth_mac));
    }
#endif
    // combine driver with netif
    ctx->eth_glue = esp_eth_new_netif_glue(ctx->eth_handle);
    ESP_ZWAY_ERRCHECK(esp_netif_attach(netif, ctx->eth_glue));
    
    // Register user defined event handers
    ESP_ZWAY_ERRCHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &eth_on_got_ip, ctx));
    
    ESP_ZWAY_ERRCHECK(esp_eth_start(ctx->eth_handle));
    return netif;
}

static void __eth_stop(_ZWayEsp32EthCtx_t *const ctx)
{
    ESP_ZWAY_ERRCHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_ETH_GOT_IP, &eth_on_got_ip));
    ESP_ZWAY_ERRCHECK(esp_eth_stop(ctx->eth_handle));
    ESP_ZWAY_ERRCHECK(esp_eth_del_netif_glue(ctx->eth_glue));
    ESP_ZWAY_ERRCHECK(esp_eth_driver_uninstall(ctx->eth_handle));
    ctx->eth_handle = NULL;
    ESP_ZWAY_ERRCHECK(ctx->phy->del(ctx->phy));
    ESP_ZWAY_ERRCHECK(ctx->mac->del(ctx->mac));
    esp_netif_destroy(ctx->netif);
}

//  tear down connection, release resources 
void _zway_esp32_ethernet_shutdown(_ZWayEsp32EthCtx_t *const ctx)
{
    if (ctx->semph_wait_ip == NULL)
    {
        return;
    }
    vSemaphoreDelete(ctx->semph_wait_ip);
    if(ctx->netif != NULL)
    {
        __eth_stop(ctx);
    }
    zfree(ctx);
}

ZWError _zway_esp32_ethernet_connect(_ZWayEsp32EthCtx_t **const ctx, const ZWLog logger)
{
    _ZWayEsp32EthCtx_t *const ctx_local = zassert(zmalloc(sizeof(_ZWayEsp32EthCtx_t)));
    if (ctx_local == NULL)
    {
        return BadAllocation;
    }
    ctx_local->logger = logger;
    ctx_local->semph_wait_ip = xSemaphoreCreateBinary();
    if (ctx_local->semph_wait_ip == NULL)
    {
        zfree(ctx_local);
        return BadAllocation;
    }
    ctx_local->netif = __eth_start(ctx_local);
    if (ctx_local->netif == NULL)
    {
        _zway_esp32_ethernet_shutdown(ctx_local);
        zfree(ctx_local);
        return InternalError;
    }
    zlog_write(ctx_local->logger, __FILE__, Information, "Ethernet. Waiting for IP...");
    *ctx = ctx_local;
    (void)xSemaphoreTake(ctx_local->semph_wait_ip, portMAX_DELAY);
    return NoError;
}
#endif // ZWAY_ON_ETHERNET
